package zone.czh.woi.woim.protocol.dectector;

import zone.czh.woi.woim.server.channel.protocol.detector.ProtocolDetector;
import zone.czh.woi.woim.server.channel.protocol.initializer.ProtocolInitializer;

import java.util.Arrays;
import java.util.Map;

/**
*@ClassName: DefaultProxyProtocolV2Detector
*@Description: None
*@author woi
*/
public class DefaultProxyProtocolV2Detector extends ProtocolDetector {

    public static final int PARSING_SIGNATURE = 0;
    public static final int PARSING_FIXED = 1;
    public static final int PARSING_ADDRESS = 2;
    public static final int FINISH_PROTOCOL_PROXY = 3;
    public static final String KEY_PROGRESS = "progress";
    public static final String KEY_VERSION = "version";
    public static final String KEY_CMD = "cmd";
    public static final String KEY_AF = "af";
    public static final int AF_INET = 0x1;
    public static final int AF_INET6 = 0x2;
    public static final String KEY_PROTO = "proto";
    public static final String KEY_ADDRESS_LEN = "address_len";
    public static final int MASK_4B = 0x0f;
    public static final int MASK_8B = 0xff;


    public static final byte[] SIGNATURE = {13,10,13,10,0,13,10,81,85,73,84,10};
    public static final int SIGNATURE_LEN =12;
    public static final int INFO_LEN = 4;

    public DefaultProxyProtocolV2Detector(){
        super(SIGNATURE_LEN);
    }

    @Override
    public ProtocolInitializer.Intent detect(byte[] bytes, ProtocolInitializer.Intent latestIntent) {
        if (latestIntent==null){
            latestIntent = new ProtocolInitializer.Intent();
            latestIntent.getExtras().put(KEY_PROGRESS, PARSING_SIGNATURE);
            latestIntent.setDetectRange(bytes.length);
        }
        Map<String, Object> extras = latestIntent.getExtras();
        Integer progress = (Integer) extras.get(KEY_PROGRESS);
        switch (progress){
            case PARSING_SIGNATURE:
                if (Arrays.equals(bytes,SIGNATURE)){
                    latestIntent.setResetReaderIndex(false);
                    latestIntent.setDetectRange(INFO_LEN);
                    latestIntent.setProxy(true);
                    extras.put(KEY_PROGRESS,PARSING_FIXED);
                }else {
                    extras.put(KEY_PROGRESS,FINISH_PROTOCOL_PROXY);
                    latestIntent.setDetectRange(2);
                    latestIntent.setProxy(false);
                    latestIntent.setResetReaderIndex(true);
                    latestIntent.setFinished(false);
                }
                break;
            case PARSING_FIXED:
                extras.put(KEY_CMD,bytes[0]&MASK_4B);
                extras.put(KEY_VERSION,(bytes[0]>>4)&MASK_4B);
                extras.put(KEY_PROTO,bytes[1]&MASK_4B);
                extras.put(KEY_AF,(bytes[1]>>4)&MASK_4B);
                int addressLen = ((bytes[2]&MASK_8B)<<8)+(bytes[3]&MASK_8B);
                extras.put(KEY_ADDRESS_LEN,addressLen);
                extras.put(KEY_PROGRESS,PARSING_ADDRESS);
                latestIntent.setResetReaderIndex(false);
                latestIntent.setDetectRange(addressLen);
                break;
            case PARSING_ADDRESS:
                if (extras.get(KEY_AF).equals(AF_INET)){
                    String realRemoteIp = getIpStr(Arrays.copyOfRange(bytes,0,4));
                    latestIntent.setRealRemoteIp(realRemoteIp);
                    latestIntent.setRealRemotePort(((bytes[8]&MASK_8B)<<8)+(bytes[9]&MASK_8B));
                    latestIntent.setResetReaderIndex(false);
                    latestIntent.setDetectRange(2);
                    extras.put(KEY_PROGRESS,FINISH_PROTOCOL_PROXY);
                }else {
                    throw new RuntimeException("unsupported address family");
                }
                break;
            case FINISH_PROTOCOL_PROXY:
                latestIntent.setResetReaderIndex(true);
                if (isHttp(bytes[0],bytes[1])){
                    latestIntent.setProtocolName("web_socket");
                }else {
                    latestIntent.setProtocolName("protobuf");
                }
                latestIntent.setFinished(true);
                break;
        }

        return latestIntent;
    }

    private String getIpStr(byte[] bytes) {
        return (bytes[0]&0xff)+"."+(bytes[1]&0xff)+"."+(bytes[2]&0xff)+"."+(bytes[3]&0xff);
    }

    private boolean isHttp(int magic1, int magic2) {
        return magic1 == 'G' && magic2 == 'E' || // GET
                magic1 == 'P' && magic2 == 'O' || // POST
                magic1 == 'P' && magic2 == 'U' || // PUT
                magic1 == 'H' && magic2 == 'E' || // HEAD
                magic1 == 'O' && magic2 == 'P' || // OPTIONS
                magic1 == 'P' && magic2 == 'A' || // PATCH
                magic1 == 'D' && magic2 == 'E' || // DELETE
                magic1 == 'T' && magic2 == 'R' || // TRACE
                magic1 == 'C' && magic2 == 'O';   // CONNECT
    }
}
